package bnb

import (
	"context"
	"crypto/ecdsa"
	"errors"
	"math/big"
	"net/http"
	"raymond/global"
	"raymond/result"
	"raymond/util"

	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/labstack/echo/v4"
	"golang.org/x/crypto/sha3"
)

type CoinTransferVO struct {
	FromPriv  string  `json:"fromPriv" validate:"required,gt=0"`
	ToAddress string  `json:"toAddress" validate:"required,min=40,max=42"`
	Amount    float64 `json:"amount" validate:"required,gt=0"`
}

type TokenTransferVO struct {
	Contract  string  `json:"contract" validate:"required,min=40,max=42"`
	FromPriv  string  `json:"fromPriv" validate:"required,gt=0"`
	ToAddress string  `json:"toAddress" validate:"required,min=40,max=42"`
	Amount    float64 `json:"amount" validate:"required,gt=0"`
}

type TransferVO struct {
	Hash string `json:"hash,omitempty"`
}

func CoinTransferAPI(c echo.Context) error {
	vo := new(CoinTransferVO)
	if err := c.Bind(vo); err != nil {
		return err
	}
	if err := c.Validate(vo); err != nil {
		return err
	}
	isValidAddress := util.IsValidAddress(vo.ToAddress)
	if isValidAddress {
		isContract, err := util.IsContractAddress(vo.ToAddress)
		if err != nil {
			return err
		}
		if isContract {
			return errors.New(result.Message[result.IS_CONTRACT_ADDRESS])
		}
	}
	txHash, err := CoinTransfer(vo.FromPriv, vo.ToAddress, vo.Amount, ETHER)
	if err != nil {
		return errors.New(err.Error())
	}
	return c.JSON(
		http.StatusOK,
		result.Ok(&TransferVO{
			Hash: txHash,
		}),
	)
}

// 本币转账
func CoinTransfer(fromPrivKey string, toAddressHex string, value float64, tokenUnit Unit) (string, error) {
	client := global.Client()

	privateKey, err := crypto.HexToECDSA(fromPrivKey)
	if err != nil {
		return "", err
	}

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		return "", errors.New("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
	}

	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
	// 每笔交易都需要一个nonce.根据定义,nonce是仅使用一次的数字.如果是发送交易的新帐户,则该随机数将为“0”.
	// 来自帐户的每个新事务都必须具有前一个nonce增加1的nonce.很难对所有nonce进行手动跟踪,于是ethereum客户端提供一个帮助方法PendingNonceAt,它将返回你应该使用的下一个nonce
	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		return "", err
	}

	amt := tokenUnit.ToWei(value)
	gasLimit := uint64(21000) * GasLimitMultiple
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		return "", err
	}

	toAddress := common.HexToAddress(toAddressHex)
	var data []byte
	tx := types.NewTransaction(nonce, toAddress, amt, gasLimit, gasPrice, data)

	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		return "", err
	}

	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		return "", err
	}

	err = client.SendTransaction(context.Background(), signedTx)
	if err != nil {
		return "", err
	}

	return signedTx.Hash().Hex(), nil
}

func TokenTransferAPI(c echo.Context) error {
	vo := new(TokenTransferVO)
	if err := c.Bind(vo); err != nil {
		return err
	}
	if err := c.Validate(vo); err != nil {
		return err
	}
	isValidContractAddr := util.IsValidAddress(vo.Contract)
	if isValidContractAddr {
		isContract, err := util.IsContractAddress(vo.Contract)
		if err != nil {
			return err
		}
		if !isContract {
			return errors.New(result.Message[result.NOT_CONTRACT_ADDRESS])
		}
	}
	isValidAddress := util.IsValidAddress(vo.ToAddress)
	if isValidAddress {
		isContract, err := util.IsContractAddress(vo.ToAddress)
		if err != nil {
			return err
		}
		if isContract {
			return errors.New(result.Message[result.IS_CONTRACT_ADDRESS])
		}
	}
	txHash, err := ERC20TokenTransfer(vo.Contract, vo.FromPriv, vo.ToAddress, vo.Amount, ETHER)
	if err != nil {
		return errors.New(err.Error())
	}
	return c.JSON(
		http.StatusOK,
		result.Ok(&TransferVO{
			Hash: txHash,
		}),
	)
}

func ERC20TokenTransfer(contract string, fromPrivKey string, toAddressHex string, value float64, tokenUnit Unit) (string, error) {
	client := global.Client()

	privateKey, err := crypto.HexToECDSA(fromPrivKey)
	if err != nil {
		return "", nil
	}

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		return "", errors.New("cannot assert type: publicKey is not of type *ecdsa.PublicKey")
	}

	fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
	nonce, err := client.PendingNonceAt(context.Background(), fromAddress)
	if err != nil {
		return "", nil
	}

	// 代币转账时, 以太币不需要转账, 设置为0
	coinValue := big.NewInt(0)
	gasPrice, err := client.SuggestGasPrice(context.Background())
	if err != nil {
		return "", nil
	}

	toAddress := common.HexToAddress(toAddressHex)
	// 代币合约地址
	tokenContractAddress := common.HexToAddress(contract)

	transferFnSignature := []byte("transfer(address,uint256)")
	hash := sha3.NewLegacyKeccak256()
	hash.Write(transferFnSignature)
	methodID := hash.Sum(nil)[:4]

	// 将接收代币的地址左填充到32字节
	paddedToAddress := common.LeftPadBytes(toAddress.Bytes(), 32)

	// 代币交易单位是Wei, 需要做一个转化
	amt := tokenUnit.ToWei(value)

	// 将代币量左填充到32字节
	paddedTokenAmount := common.LeftPadBytes(amt.Bytes(), 32)

	var data []byte
	data = append(data, methodID...)
	data = append(data, paddedToAddress...)
	data = append(data, paddedTokenAmount...)
	gasLimit, err := client.EstimateGas(context.Background(), ethereum.CallMsg{
		To:   &toAddress,
		Data: data,
	})
	if err != nil {
		return "", nil
	}

	gasLimit = gasLimit * GasLimitMultiple

	tx := types.NewTransaction(nonce, tokenContractAddress, coinValue, gasLimit, gasPrice, data)

	chainID, err := client.NetworkID(context.Background())
	if err != nil {
		return "", nil
	}

	signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), privateKey)
	if err != nil {
		return "", nil
	}

	err = client.SendTransaction(context.Background(), signedTx)
	if err != nil {
		return "", nil
	}

	return signedTx.Hash().Hex(), nil
}
